package org.hepeng.workx.spring.cloud.netflix.zuul.filter;

import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;
import org.apache.commons.lang3.StringUtils;
import org.hepeng.workx.exception.ApplicationRuntimeException;
import org.hepeng.workx.spring.cloud.netflix.ribbon.RibbonCommandContextHolder;
import org.hepeng.workx.spring.cloud.netflix.ribbon.SafeRibbonCommandContext;
import org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;

/**
 * @author he peng
 */
public class RibbonRoutingFilterProxy implements MethodHandler {

    private static final RibbonRoutingFilterProxy RIBBON_ROUTING_FILTER_PROXY = new RibbonRoutingFilterProxy();

    public static RibbonRoutingFilter proxy(ProxyRequestHelper helper , RibbonCommandFactory factory , List<RibbonRequestCustomizer> requestCustomizers) {
        ProxyFactory enhancer = new ProxyFactory();
        enhancer.setSuperclass(RibbonRoutingFilter.class);

        try {
            Constructor<RibbonRoutingFilter> constructor =
                    RibbonRoutingFilter.class.getConstructor(ProxyRequestHelper.class, RibbonCommandFactory.class, List.class);
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            Object[] values = {helper , factory , requestCustomizers};
            Object enhanced = enhancer.create(parameterTypes, values);
            ((Proxy) enhanced).setHandler(RIBBON_ROUTING_FILTER_PROXY);
            return (RibbonRoutingFilter) enhanced;
        } catch (Throwable t) {
            throw new ApplicationRuntimeException("create " + RibbonRoutingFilter.class + " proxy error" , t);
        }
    }

    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        if (! StringUtils.equals(thisMethod.getName() , "forward")) {
            return proceed.invoke(self , args);
        }

        Object obj;
        try {
            SafeRibbonCommandContext safeRibbonCommandContext = SafeRibbonCommandContext.copy(args[0]);
            RibbonCommandContextHolder.set(safeRibbonCommandContext);
            obj = proceed.invoke(self , args);
        } finally {
            RibbonCommandContextHolder.clear();
        }
        return obj;
    }
}
